#include "versionmanager.h"
#include <wx/wx.h>
#include <wx/xrc/xmlres.h>
#include <wx/filefn.h>
#include "svn_cmd.h"
#include "workspace.h"
#include "vm_manager.h"
#include "vm_version.h"

// The plugin
static VersionManager* thePlugin = NULL;

// UNIQUES ID
static const int ID_VM_POPUP         = XRCID("vm_popup");
static const int ID_VM_PROJ_SETTINGS = XRCID("vm_project_settings");
static const int ID_VM_GLOB_SETTINGS = XRCID("vm_global_settings");
static const int ID_VM_DELIVER       = XRCID("vm_deliver");

// Serialization info
const wxString VM_PLUGIN_NAME            = wxT("versionmanager");
const wxString VM_VERSION_MAJOR          = wxT("version.major");
const wxString VM_VERSION_MINOR          = wxT("version.minor");
const wxString VM_VERSION_RELEASE        = wxT("version.release");
const wxString VM_VERSION_BUILD          = wxT("version.build");
const wxString VM_VERSION_STATUS         = wxT("version.status");
const wxString VM_SETTING_FILENAME       = wxT("setting.filename");
const wxString VM_SETTING_MAJOR          = wxT("setting.major");
const wxString VM_SETTING_MINOR          = wxT("setting.minor");
const wxString VM_SETTING_RELEASE        = wxT("setting.release");
const wxString VM_SETTING_BUILD          = wxT("setting.build");
const wxString VM_SETTING_STATUS         = wxT("setting.status");
const wxString VM_SETTING_VERSION        = wxT("setting.version");
const wxString VM_SETTING_USE_NAMESPACE  = wxT("setting.use-namespace");
const wxString VM_SETTING_NAMESPACE      = wxT("setting.namespace");
const wxString VM_SETTING_USE_SVN_BUILD  = wxT("setting.use-svn-build");
const wxString VM_SVN_PATH               = wxT("svn-path");
const wxString VM_SETTING_USE_EXT_FILE   = wxT("setting.use-ext-settings");

void VMProjectSettings::Serialize(Archive &arch)
{
	// Version
	arch.Write(VM_VERSION_MAJOR,   m_versionData.m_version.m_major);
	arch.Write(VM_VERSION_MINOR,   m_versionData.m_version.m_minor);
	arch.Write(VM_VERSION_RELEASE, m_versionData.m_version.m_release);
	arch.Write(VM_VERSION_BUILD,   m_versionData.m_version.m_build);
	arch.Write(VM_VERSION_STATUS,  m_versionData.m_version.m_status);
	// Settings
	arch.Write(VM_SETTING_FILENAME,      m_versionData.m_settings.m_sFilename);
	arch.Write(VM_SETTING_MAJOR,         m_versionData.m_settings.m_sMajor);
	arch.Write(VM_SETTING_MINOR,         m_versionData.m_settings.m_sMinor);
	arch.Write(VM_SETTING_RELEASE,       m_versionData.m_settings.m_sRelease);
	arch.Write(VM_SETTING_BUILD,         m_versionData.m_settings.m_sBuild);
	arch.Write(VM_SETTING_STATUS,        m_versionData.m_settings.m_sStatus);
	arch.Write(VM_SETTING_VERSION,       m_versionData.m_settings.m_sVersion);
	arch.Write(VM_SETTING_USE_NAMESPACE, m_versionData.m_settings.m_bUseNamespace);
	arch.Write(VM_SETTING_NAMESPACE,     m_versionData.m_settings.m_sNamespace);
	arch.Write(VM_SETTING_USE_SVN_BUILD, m_versionData.m_settings.m_bUseBuildSvnRev);
}

void VMProjectSettings::DeSerialize(Archive &arch)
{
	// Version
	arch.Read(VM_VERSION_MAJOR,   m_versionData.m_version.m_major);
	arch.Read(VM_VERSION_MINOR,   m_versionData.m_version.m_minor);
	arch.Read(VM_VERSION_RELEASE, m_versionData.m_version.m_release);
	arch.Read(VM_VERSION_BUILD,   m_versionData.m_version.m_build);
	arch.Read(VM_VERSION_STATUS,  m_versionData.m_version.m_status);
	// Settings
	arch.Read(VM_SETTING_FILENAME,      m_versionData.m_settings.m_sFilename);
	arch.Read(VM_SETTING_MAJOR,         m_versionData.m_settings.m_sMajor);
	arch.Read(VM_SETTING_MINOR,         m_versionData.m_settings.m_sMinor);
	arch.Read(VM_SETTING_RELEASE,       m_versionData.m_settings.m_sRelease);
	arch.Read(VM_SETTING_BUILD,         m_versionData.m_settings.m_sBuild);
	arch.Read(VM_SETTING_STATUS,        m_versionData.m_settings.m_sStatus);
	arch.Read(VM_SETTING_VERSION,       m_versionData.m_settings.m_sVersion);
	arch.Read(VM_SETTING_USE_NAMESPACE, m_versionData.m_settings.m_bUseNamespace);
	arch.Read(VM_SETTING_NAMESPACE,     m_versionData.m_settings.m_sNamespace);
	arch.Read(VM_SETTING_USE_SVN_BUILD, m_versionData.m_settings.m_bUseBuildSvnRev);
}

void VMGlobalSettings::Serialize(Archive &arch)
{
	arch.Write(VM_SVN_PATH,              m_sSvnPath);
	// Settings
	arch.Write(VM_SETTING_FILENAME,      m_settings.m_sFilename);
	arch.Write(VM_SETTING_MAJOR,         m_settings.m_sMajor);
	arch.Write(VM_SETTING_MINOR,         m_settings.m_sMinor);
	arch.Write(VM_SETTING_RELEASE,       m_settings.m_sRelease);
	arch.Write(VM_SETTING_BUILD,         m_settings.m_sBuild);
	arch.Write(VM_SETTING_STATUS,        m_settings.m_sStatus);
	arch.Write(VM_SETTING_VERSION,       m_settings.m_sVersion);
	arch.Write(VM_SETTING_USE_NAMESPACE, m_settings.m_bUseNamespace);
	arch.Write(VM_SETTING_NAMESPACE,     m_settings.m_sNamespace);
	arch.Write(VM_SETTING_USE_SVN_BUILD, m_settings.m_bUseBuildSvnRev);
	arch.Write(VM_SETTING_USE_EXT_FILE,  m_bUseExternalSettingFile);
}

void VMGlobalSettings::DeSerialize(Archive &arch)
{
	arch.Read(VM_SVN_PATH,              m_sSvnPath);
	// Settings
	arch.Read(VM_SETTING_FILENAME,      m_settings.m_sFilename);
	arch.Read(VM_SETTING_MAJOR,         m_settings.m_sMajor);
	arch.Read(VM_SETTING_MINOR,         m_settings.m_sMinor);
	arch.Read(VM_SETTING_RELEASE,       m_settings.m_sRelease);
	arch.Read(VM_SETTING_BUILD,         m_settings.m_sBuild);
	arch.Read(VM_SETTING_STATUS,        m_settings.m_sStatus);
	arch.Read(VM_SETTING_VERSION,       m_settings.m_sVersion);
	arch.Read(VM_SETTING_USE_NAMESPACE, m_settings.m_bUseNamespace);
	arch.Read(VM_SETTING_NAMESPACE,     m_settings.m_sNamespace);
	arch.Read(VM_SETTING_USE_SVN_BUILD, m_settings.m_bUseBuildSvnRev);
	arch.Read(VM_SETTING_USE_EXT_FILE,  m_bUseExternalSettingFile);
}

VMProject::VMProject(IManager* mgr, ProjectPtr pProject)
 : m_mgr(mgr)
 , m_pProject(pProject)
 , m_bIsManaged(false)
 , m_bUseExternalSettingFile(true)
{
	// Load global settings
	VMGlobalSettings globalSettings;
	m_mgr->GetConfigTool()->ReadObject(VM_PLUGIN_NAME, &globalSettings);
	m_data.GetVersionData().m_settings = globalSettings.GetSettings();
	// override global setings with project settings
	m_bIsManaged = Load();
}

bool VMProject::Load()
{
	bool bLoaded = false;
	
	wxFileName fnVMProjectSettings = m_pProject->GetFileName();
	fnVMProjectSettings.SetExt(wxT("versionmanager"));
	if (fnVMProjectSettings.FileExists()) {
		// Use external file
		wxXmlDocument doc;
		if (!doc.Load(fnVMProjectSettings.GetFullPath())) {
			return false;
		}
		
		if (doc.GetRoot()->GetName() != wxT("versionmanager")) {
			return false;
		}
		
		m_bUseExternalSettingFile = true;

		Archive arch;
		arch.SetXmlNode(doc.GetRoot());
		m_data.DeSerialize(arch);
		bLoaded = true;
	}
	else {
		// Use project
		m_bUseExternalSettingFile = false;
		bLoaded = m_pProject->GetUserData(VM_PLUGIN_NAME, &m_data);
	}
	return bLoaded;
}

bool VMProject::Save(bool bWriteFile)
{
	bool bSaved = false;
	wxFileName fnVMProjectSettings = m_pProject->GetFileName();
	fnVMProjectSettings.SetExt(wxT("versionmanager"));
	if (m_bUseExternalSettingFile) {
		wxXmlNode* root = new wxXmlNode(NULL, wxXML_ELEMENT_NODE, wxT("versionmanager"));
		Archive arch;
		arch.SetXmlNode(root);
		m_data.Serialize(arch);
		wxXmlDocument doc;
		doc.SetRoot(root);
		bSaved = doc.Save(fnVMProjectSettings.GetFullPath());
	}
	else {
		::wxRemoveFile(fnVMProjectSettings.GetFullPath());
		bSaved = m_pProject->SetUserData(VM_PLUGIN_NAME, &m_data);
	}
	m_bIsManaged = bSaved;
	if (bSaved && bWriteFile)
	{
		// Create the absolute version path
		versionmgr::VersionData newVersionData = m_data.GetVersionData();
		newVersionData.m_settings.m_sFilename = GetVersionFullPath();

		// Add this file to project if it doesn't exists
		if (m_pProject->IsFileExist(newVersionData.m_settings.m_sFilename) == false)
		{
			m_mgr->CreateVirtualDirectory(GetName(), wxT("version"));
			wxString sVD = wxString::Format(wxT("%s:version"), GetName().c_str());
			wxArrayString arrayFiles;
			arrayFiles.Add(newVersionData.m_settings.m_sFilename);
			m_mgr->AddFilesToVirtualFolder(sVD, arrayFiles);
		}
		bSaved = versionmgr::WriteVersionFile(newVersionData);
	}
	return bSaved;
}

/**
 * Get the full absolute path of version file, according to the project path
 *
 * @return the absolute version file path
 */
wxString VMProject::GetVersionFullPath() const
{
	// Create the absolute version file name, according to the project path
	wxFileName fnVersionFilename(GetFileName().GetPath(wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR) + m_data.GetVersionData().m_settings.m_sFilename);
	fnVersionFilename.Normalize();
	return fnVersionFilename.GetFullPath();
}


/**
 * Called by CodeLite to create the plugin
 *
 * @param manager The manager
 *
 * @return The plugin
 */
extern "C" EXPORT IPlugin *CreatePlugin(IManager *manager)
{
	if (thePlugin == 0) {
		thePlugin = new VersionManager(manager);
	}
	return thePlugin;
}

/**
 * Called by CodeLite to get the plugin informations
 *
 * @return The plugin informations
 */
extern "C" EXPORT PluginInfo GetPluginInfo()
{
	PluginInfo info;
	info.SetAuthor(wxT("Jérémie Fouché"));
	info.SetName(wxT("VersionManager"));
	info.SetDescription(wxT("Manage you project version"));
	info.SetVersion( wxString(version::VERSION, wxConvLocal) );
	return info;
}

/**
 * Called by CodeLite to get the plugin interface version
 * this version is used to check that plugin and CL has the same interface
 *
 * @return The plugin interface version
 */
extern "C" EXPORT int GetPluginInterfaceVersion()
{
	return PLUGIN_INTERFACE_VERSION;
}


/**
 * VersionManager constructor
 */
VersionManager::VersionManager(IManager *manager)
: IPlugin(manager)
{
	m_longName  = wxT("Manage Version");
	m_shortName = wxT("VersionManager");

	// Set SVN
	VMGlobalSettings globalSettings;
	m_mgr->GetConfigTool()->ReadObject(VM_PLUGIN_NAME, &globalSettings);
	svn::set_svn_cmd(globalSettings.GetSvnPath());

	m_mgr->GetTheApp()->Connect(wxEVT_BUILD_STARTING, wxCommandEventHandler(VersionManager::OnBuildStarting), NULL, this);

	m_mgr->GetTheApp()->Connect(ID_VM_PROJ_SETTINGS, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(VersionManager::OnProjectSettings), NULL, this);
	m_mgr->GetTheApp()->Connect(ID_VM_GLOB_SETTINGS, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(VersionManager::OnGlobalSettings), NULL, this);
	m_mgr->GetTheApp()->Connect(ID_VM_DELIVER,       wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(VersionManager::OnDeliver), NULL, this);
}

VersionManager::~VersionManager()
{
}

clToolBar* VersionManager::CreateToolBar(wxWindow *parent)
{
	return NULL;
}

void VersionManager::CreatePluginMenu(wxMenu *pluginsMenu)
{
	wxMenu *menu = new wxMenu();
	wxMenuItem *item(NULL);
	item = new wxMenuItem(menu, ID_VM_GLOB_SETTINGS, _("Settings..."), wxEmptyString, wxITEM_NORMAL);
	menu->Append(item);

	pluginsMenu->Append(wxID_ANY, _("VersionManager"), menu);
}

void VersionManager::HookPopupMenu(wxMenu *menu, MenuType type)
{
	if (type == MenuTypeEditor) {
		//TODO::Append items for the editor context menu
	} else if (type == MenuTypeFileExplorer) {
		//TODO::Append items for the file explorer context menu
	} else if (type == MenuTypeFileView_Workspace) {
		//TODO::Append items for the file view / workspace context menu
	} else if (type == MenuTypeFileView_Project) {
		//TODO::Append items for the file view/Project context menu
		if (!menu->FindItem(ID_VM_POPUP)) {
			m_separatorItem = menu->AppendSeparator();
			wxMenu* menuVM = new wxMenu();
			menuVM->Append( new wxMenuItem(menu, ID_VM_PROJ_SETTINGS, wxT("Settings ..."), wxEmptyString, wxITEM_NORMAL) );
			menuVM->Append( new wxMenuItem(menu, ID_VM_DELIVER, wxT("Deliver ..."), wxEmptyString, wxITEM_NORMAL) );
			menu->Append(ID_VM_POPUP, wxT("Version manager"), menuVM);
		}
	} else if (type == MenuTypeFileView_Folder) {
		//TODO::Append items for the file view/Virtual folder context menu
	} else if (type == MenuTypeFileView_File) {
		//TODO::Append items for the file view/file context menu
	}
}

void VersionManager::UnHookPopupMenu(wxMenu *menu, MenuType type)
{
	if (type == MenuTypeEditor) {
		//TODO::Unhook items for the editor context menu
	} else if (type == MenuTypeFileExplorer) {
		//TODO::Unhook  items for the file explorer context menu
	} else if (type == MenuTypeFileView_Workspace) {
		//TODO::Unhook  items for the file view / workspace context menu
	} else if (type == MenuTypeFileView_Project) {
		//TODO::Unhook  items for the file view/Project context menu
		wxMenuItem *item = menu->FindItem(ID_VM_POPUP);
		if (item) {
			menu->Destroy(item);
			menu->Destroy(m_separatorItem);
			m_separatorItem = NULL;
		}
	} else if (type == MenuTypeFileView_Folder) {
		//TODO::Unhook  items for the file view/Virtual folder context menu
	} else if (type == MenuTypeFileView_File) {
		//TODO::Unhook  items for the file view/file context menu
	}
}

void VersionManager::UnPlug()
{
	//TODO:: perform the unplug action for this plugin
}

/**
 * Get the project actually selected by the user
 *
 * @return the project
 */
ProjectPtr VersionManager::GetSelectedProject() const
{
	TreeItemInfo infos = m_mgr->GetSelectedTreeItemInfo(TreeFileView);
	wxString sProjectName = infos.m_text;
	Workspace* pWorkspace = m_mgr->GetWorkspace();
	wxString sError;
	return pWorkspace->FindProjectByName(sProjectName, sError);
}

/**
 * Called by the framework when the user click on Plugins -> Version Manager -> Settings
 * This method allow set default values for Version managing
 *
 * @param event
 */
void VersionManager::OnGlobalSettings(wxCommandEvent& event)
{
	VMGlobalSettings globalSettings;
	m_mgr->GetConfigTool()->ReadObject(VM_PLUGIN_NAME, &globalSettings);
	versionmgr::GlobalSettingsDialog settingsDlg(GetTopWindow(), globalSettings.GetSettings(), globalSettings.UseExternalSettingFile(), m_mgr->GetConfigTool());
	if (settingsDlg.ShowModal() == wxID_OK) {
		globalSettings.SetSettings(settingsDlg.GetSettings());
		globalSettings.UseExternalSettingFile(settingsDlg.UseExternalSettingFile());
		globalSettings.SetSvnPath(svn::get_svn_cmd());
		m_mgr->GetConfigTool()->WriteObject(VM_PLUGIN_NAME, &globalSettings);
	}
}

/**
 * Called by the framework when the user click on Version Manager -> Settings
 * This method allow user to begin version management if it is not actually the case,
 * and change settings
 *
 * @param event
 */
void VersionManager::OnProjectSettings(wxCommandEvent& event)
{
	ProjectPtr pProject = GetSelectedProject();
	if (!pProject) {
		return;
	}

	// Retrieve version data from project
	VMProject vmProject(m_mgr, pProject);
	if (!vmProject.IsManaged()) {
		wxString sQuestion = wxString::Format(_("Do you want to manage version for project : %s ?"), vmProject.GetName().c_str());
		// This project doesn't manage version
		if (wxMessageBox(sQuestion, _("Version Manager"), wxYES_NO|wxICON_QUESTION) != wxYES)  {
			return;
		}
	}

	versionmgr::ProjectSettingsDialog settingsDlg(GetTopWindow(), vmProject.GetFileName().GetPath(), vmProject.GetVersionData().m_settings, vmProject.UseExternalSettingFile(), m_mgr->GetConfigTool());
	if (settingsDlg.ShowModal() == wxID_OK) {
		vmProject.UseExternalSettingFile(settingsDlg.UseExternalSettingFile());
		// Store file name in the project
		vmProject.GetVersionData().m_settings = settingsDlg.GetSettings();
		vmProject.Save();
	}
}

/**
 * Called by the framework when the user click on Version Manager -> Deliver
 *
 * @param event
 */
void VersionManager::OnDeliver(wxCommandEvent& event)
{
	ProjectPtr pProject = GetSelectedProject();
	if (!pProject) {
		return;
	}

	// Retrieve version data from project
	VMProject vmProject(m_mgr, pProject);
	if (!vmProject.IsManaged()) {
		return;
	}

	versionmgr::VersionDialog versionDlg(GetTopWindow(), vmProject.GetVersionData(), m_mgr->GetConfigTool());
	if (versionDlg.ShowModal() == wxID_OK) {
		vmProject.GetVersionData().m_version = versionDlg.GetVersion();
		vmProject.Save();
	}
}

/**
 * Called by the framework when a build process is going to start
 *
 * @param event Event containing the project name as client data
 */
void VersionManager::OnBuildStarting(wxCommandEvent& event)
{
	// Allow everybody to process this message
	event.Skip();
	
	wxString* pProjectName = static_cast<wxString*>(event.GetClientData());
	if (!pProjectName) {
		return;
	}

	Workspace* pWorkspace = m_mgr->GetWorkspace();
	wxString sError;
	ProjectPtr pProject = pWorkspace->FindProjectByName(*pProjectName, sError);
	if (!pProject)
		return;

	VMProject vmProject(m_mgr, pProject);
	if (vmProject.IsManaged()) {
		vmProject.Save();
    }

	// TODO : manage dependencies
}
